package org.duang.http.core;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.conn.NHttpClientConnectionManager;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.duang.http.common.ClientException;
import org.duang.http.common.RepeatableInputStreamEntity;
import org.duang.http.core.ServiceClient.Request;
import org.duang.http.core.enums.HttpMethod;
import org.duang.kit.LogKit;
import org.duang.kit.ToolsKit;
import org.duang.logs.Logger;

import com.aliyun.mns.common.http.HttpFactory.IdleConnectionMonitor;
import com.google.common.net.HttpHeaders;

public class HttpFactory {

	private static Logger logger = LogKit.getLogger(HttpFactory.class);

	public  static PoolingNHttpClientConnectionManager getConnectionManager(ClientConfig config) {
//		ClientPNames.CONN_MANAGER_TIMEOUT;
			IOReactorConfig ioConfig = IOReactorConfig.custom().setIoThreadCount(config.getIoReactorThreadCount())
					.setConnectTimeout(config.getConnectionTimeout()).setSoTimeout(config.getSocketTimeout())
					.setSoKeepAlive(config.isSoKeepAlive()).build();
			ConnectingIOReactor ioReactor = null;
			try {
				ioReactor = new DefaultConnectingIOReactor(ioConfig);
			} catch (IOReactorException e) {
				throw new ClientException(e.getMessage(), e);
			}
			if (ToolsKit.isEmpty(ioReactor)) {
				throw new ClientException("ConnectingIOReactor is null");
			}
			PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
			connManager.setDefaultMaxPerRoute(config.getMaxConnectionsPerRoute());
			connManager.setMaxTotal(config.getMaxConnections());
		return connManager;
	}

	private static RequestConfig getRequestConfig(ClientConfig config) {
		RequestConfig requestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.BEST_MATCH).setExpectContinueEnabled(true)
					.setStaleConnectionCheckEnabled(true)
					.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST))
					.setConnectTimeout(config.getConnectionTimeout()).setSocketTimeout(config.getSocketTimeout())
					.setExpectContinueEnabled(config.isExceptContinue()).build();
		return requestConfig;
	}

	public static CloseableHttpAsyncClient createHttpAsyncClient(PoolingNHttpClientConnectionManager connManager, ClientConfig config) {
		HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClients.custom()
				.setConnectionManager(connManager);
		httpClientBuilder.setDefaultRequestConfig(getRequestConfig(config))
				.setMaxConnPerRoute(config.getMaxConnectionsPerRoute()).setMaxConnTotal(config.getMaxConnections())
				.setUserAgent(config.getUserAgent());
		CloseableHttpAsyncClient httpClient = httpClientBuilder.build();
//		logger.print("createHttpAsyncClient : "+  httpClient.hashCode() + " is success!");
		return httpClient;
	}

	private static SSLConnectionSocketFactory getSSLSocketFactory() {
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            public void checkClientTrusted(
                    java.security.cert.X509Certificate[] certs, String authType) {
            }

            public void checkServerTrusted(
                    java.security.cert.X509Certificate[] certs, String authType) {
            }
        }};

        try {
            SSLContext sslcontext = SSLContext.getInstance("SSL");
            sslcontext.init(null, trustAllCerts, null);
            SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(
                    sslcontext,
                    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            return ssf;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

	public static HttpRequestBase createHttpRequest(Request request) {
		String url = request.getUrl();
		HttpMethod method = request.getMethod();
		HttpRequestBase httpRequestBase;
		if (method == HttpMethod.GET) {
			httpRequestBase = new HttpGet(url);
		} else if (method == HttpMethod.POST) {
			HttpPost postMethod = new HttpPost(url);
			if (ToolsKit.isNotEmpty(request.getContent())) {
				postMethod.setEntity(new RepeatableInputStreamEntity(request));
			}
			httpRequestBase = postMethod;
		} else if (method == HttpMethod.PUT) {
			HttpPut putMethod = new HttpPut(url);
			if (ToolsKit.isNotEmpty(request.getContent())) {
				putMethod.setEntity(new RepeatableInputStreamEntity(request));
			}
			httpRequestBase = putMethod;
		} else if (method == HttpMethod.DELETE) {
			httpRequestBase = new HttpDelete(url);
		} else if (method == HttpMethod.HEAD) {
			httpRequestBase = new HttpHead(url);
		} else if (method == HttpMethod.OPTIONS) {
			httpRequestBase = new HttpOptions(url);
		} else {
			throw new ClientException(String.format("Unsupported HTTP method: %s.", request.getMethod().toString()));
		}
		setRequestHeaders(request, httpRequestBase);
		return httpRequestBase;
	}
	
	private static void setRequestHeaders(Request request, HttpRequestBase httpRequestBase) {
		for(Iterator<Entry<String,String>> it = request.getHeaders().entrySet().iterator(); it.hasNext();) {
			Entry<String,String> entry = it.next();
			String key = entry.getKey();
			//将以下两个字段过滤
			if(key.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH) || key.equalsIgnoreCase(HttpHeaders.HOST)){
				continue;
			}
			httpRequestBase.addHeader(key, entry.getValue());
		}
//		if (httpRequestBase.getHeaders(HttpHeaders.CONTENT_TYPE) == null ||
//				httpRequestBase.getHeaders(HttpHeaders.CONTENT_TYPE).length == 0){
//			httpRequestBase.addHeader(HttpHeaders.CONTENT_TYPE, HttpUtil.UTF8_CHARSET);
//        }
	}
	
	
	
	 public static class IdleConnectionMonitor extends Thread {
	        private static final IdleConnectionMonitor instance = new IdleConnectionMonitor();
	        private final List<NHttpClientConnectionManager> connMgrs = new CopyOnWriteArrayList<NHttpClientConnectionManager>();
	        private volatile boolean shutdown = false;

	        private IdleConnectionMonitor() {
	            this.setName("IdleConnectionMonitorThread");
	            this.setDaemon(true);
	            this.start();
	        }

	        public static IdleConnectionMonitor getInstance() {
	            return instance;
	        }

	        public void addConnMgr(NHttpClientConnectionManager connMgr) {
	            connMgrs.add(connMgr);
	        }

	        public void removeConnMgr(NHttpClientConnectionManager connMgr) {
	            connMgrs.remove(connMgr);
	        }

	        @Override
	        public void run() {
	            try {
	                while (!shutdown) {
	                    sleep(15000);
	                    List<NHttpClientConnectionManager> tmpConnMgrs;
	                    synchronized (IdleConnectionMonitor.class) {
	                        tmpConnMgrs = new CopyOnWriteArrayList<NHttpClientConnectionManager>(connMgrs);
	                    }
	                    for (NHttpClientConnectionManager connMgr : tmpConnMgrs) {
	                        // Close expired connections
	                        connMgr.closeExpiredConnections();
	                        // Optionally, close connections
	                        // that have been idle longer than 30 sec
	                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
//	                        System.out.println("#################################close Idle Connections: " + connMgr.hashCode());
	                    }
	                }
	            } catch (InterruptedException ex) {
	                // terminate
	            }
	        }

	        synchronized public void shutdown() {
	            shutdown = true;
	        }

	    }
	
}
